﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.ComponentModel.DataAnnotations;
using System.Xml.Serialization;
using System.Runtime.Serialization;
using ParieurPro.DAL.DatabaseModel;
using System.Data.SqlClient;
using System.Net.Mail;

namespace ParieurPro.DAL
{

    /// <summary>
    /// Classe gestion des paris
    /// Permet de : 
    ///  - récupérer la liste des paris pour un événement ou pour un match
    ///  - Parier sur un événement
    /// </summary>
    [Serializable()]
    public class BetObject
    {
        #region Enum

        /// <summary>
        /// Statuts de la validation des paris :
        /// - Success : Paris validés
        /// - DateExpired : Trop tard pour parier pour cet événement
        /// - DataError : Paris incohérents
        /// - UnknownUser : Parieur inconnu
        /// - GeneralFailure : Erreur inconnue
        /// </summary>
        public enum PredictionValidationStatus
        {
            Success = 0,
            DateExpired = 1,
            DataError = 2,
            UnknownUser = 3,
            GeneralFailure = 100
        }

        #endregion

        #region Attributes
        private int _matchId;
        private int _teamHomeScore;
        private int _teamAwayScore;
        #endregion

        #region Properties

        /// <summary>
        /// Identifiant unique du match
        /// parié
        /// </summary>
        [Key()]
        [DataMemberAttribute()]
        public int MatchId
        {
            get { return _matchId; }
            set { _matchId = value; }
        }

        /// <summary>
        /// Pari sur le score de l'équipe qui reçoit
        /// </summary>
        [DataMemberAttribute()]
        public int TeamHomeScore
        {
            get { return _teamHomeScore; }
            set { _teamHomeScore = value; }
        }

        /// <summary>
        /// Pari sur le score de l'équipe visiteur
        /// </summary>
        [DataMemberAttribute()]
        public int TeamAwayScore
        {
            get { return _teamAwayScore; }
            set { _teamAwayScore = value; }
        }

        #endregion

        #region Data access

        /// <summary>
        /// Copie l'objet représentant la table "UserBets" de la base de données
        /// en objet "BetObject"
        /// </summary>
        /// <param name="dataToCopy">Objet à copier</param>
        /// <returns>Objet copié</returns>
        /// <exception cref="ArgumentException">Objet à copier vaut null</exception>
        public static BetObject CopyFromDatabaseObject(UserBets dataToCopy)
        {
            if (dataToCopy == null)
            {
                throw new ArgumentException("BetObject.CopyFromDatabaseObject - dataToCopy can not be null");
            }
            BetObject betTgt = new BetObject();
            betTgt.MatchId = dataToCopy.MatchId;
            betTgt.TeamHomeScore = dataToCopy.ScoreHome;
            betTgt.TeamAwayScore = dataToCopy.ScoreAway;
            return betTgt;
        }

        /// <summary>
        /// Renvoie la liste des paris d'un joueur pour une journée
        /// </summary>
        /// <param name="dbContext">Connexion à la base de données</param>
        /// <param name="userId">GUID de l'utilisateur</param>
        /// <param name="eventId">Identifiant de l'événement</param>
        /// <returns>Liste des paris, la liste est vide si l'événement n'existe pas</returns>
        /// <exception cref="ArgumentException">Connexion à la base de données vaut null</exception>
        public static IList<UserBets> GetBetsInDatabase(MyBetDBEntities dbContext, Guid userId, int eventId)
        {
            if (dbContext == null)
            {
                throw new ArgumentException("BetObject.GetBetsInDatabase - dbContext can not be null");
            }
            var queryBets = (from bets in dbContext.UserBets
                             where (bets.Matchs.EventId == eventId) && (bets.UserId == userId)
                             select bets);

            IList<UserBets> betList = queryBets.ToList();
            return betList;
        }

        /// <summary>
        /// Renvoie la liste des paris d'un joueur pour une journée
        /// </summary>
        /// <param name="dbContext">Connexion à la base de données</param>
        /// <param name="userId">GUID de l'utilisateur</param>
        /// <param name="eventId">Identifiant de l'événement</param>
        /// <returns>Liste des paris sous la forme d'un dictionnaire, la liste est vide si l'événement n'existe pas</returns>
        /// <exception cref="ArgumentException">Connexion à la base de données vaut null</exception>
        public static Dictionary<int, UserBets> GetBetsDictionaryInDatabase(MyBetDBEntities dbContext, Guid userId, int eventId)
        {
            if (dbContext == null)
            {
                throw new ArgumentException("BetObject.GetBetsDictionaryInDatabase - dbContext can not be null");
            }
            Dictionary<int, UserBets> returnList = new Dictionary<int, UserBets>();
            IList<UserBets> betList = null;

            var queryBets = (from bets in dbContext.UserBets
                             where (bets.Matchs.EventId == eventId) && (bets.UserId == userId)
                             select bets);

            betList = queryBets.ToList();

            foreach (UserBets item in betList)
            {
                returnList.Add(item.MatchId, item);
            }
            return returnList;
        }

        /// <summary>
        /// Renvoie tous les paris pour un événement donné
        /// </summary>
        /// <param name="dbContext">Connexion à la base de données</param>
        /// <param name="championshipId">Identifiant du championnat</param>
        /// <param name="dayNumber">Numéro de la journée</param>
        /// <param name="seasonId">Identifiant de la saison</param>
        /// <returns>Liste de tous les paris</returns>
        /// <exception cref="ArgumentException">Connexion à la base de données vaut null</exception>
        public static IList<UserBets> GetAllBetsInDatabase(MyBetDBEntities dbContext, int championshipId, int dayNumber, int seasonId)
        {
            if (dbContext == null)
            {
                throw new ArgumentException("BetObject.GetBetsInDatabase - dbContext can not be null");
            }
            // Récupération de tous les paris de tous les joueurs quelque soit leur compétition
            var queryBets = (from bets in dbContext.UserBets
                             where (bets.Matchs.Events.Contests.ChampionshipId == championshipId) && (bets.Matchs.Events.DayNumber == dayNumber) && (bets.Matchs.Events.Contests.SeasonId == seasonId)
                             orderby bets.aspnet_Users.UserName
                             select bets);
            // Retour de la liste
            IList<UserBets> betList = queryBets.ToList();
            return betList;
        }

        /// <summary>
        /// Renvoie tous les paris pour un événement donné
        /// </summary>
        /// <param name="dbContext">Connexion à la base de données</param>
        /// <param name="championshipId">Identifiant du championnat</param>
        /// <param name="dayNumber">Numéro de la journée</param>
        /// <param name="seasonId">Identifiant de la saison</param>
        /// <returns>Liste de tous les paris</returns>
        /// <exception cref="ArgumentException">Connexion à la base de données vaut null</exception>
        public static IList<UserBets> GetCustomContestBetsInDatabase(MyBetDBEntities dbContext, int championshipId, int seasonId, int dayNumber, int customContest)
        {
            if (dbContext == null)
            {
                throw new ArgumentException("BetObject.GetCustomContestBetsInDatabase - dbContext can not be null");
            }

            object[] parameters = 
            {
                new SqlParameter { ParameterName = "p0", Value = championshipId },
                new SqlParameter { ParameterName = "p1", Value = seasonId },
                new SqlParameter { ParameterName = "p2", Value = dayNumber },
                new SqlParameter { ParameterName = "p3", Value = customContest }
            };
            // Récupération de tous les paris de tous les joueurs quelque soit leur compétition
            IList<UserBets> queryResult = dbContext.ExecuteStoreQuery<UserBets>
                (@"SELECT ub.UserId, ub.MatchId, ub.ScoreHome, ub.ScoreAway, ub.Point FROM [UserBets] ub
                   LEFT JOIN [Contests] c on c.ChampionshipId=@p0 
                   LEFT JOIN [Events] evt on evt.ContestId=c.ContestId
                   LEFT JOIN [Matchs] m on m.MatchId=ub.MatchId
                   where ub.UserId in (SELECT UserId FROM Subscriptions WHERE CustomContestId=@p3)
                   and m.EventId=evt.EventId and c.SeasonId=@p1 and evt.DayNumber=@p2 order by ub.UserId, ub.MatchId;", parameters).ToList();

            // Retour de la liste
            //IList<UserBets> betList = queryResult.ToList();
            return queryResult;
        }

        /// <summary>
        /// Mise à jour du score d'un pari
        /// </summary>
        /// <param name="dbContext">Connexion à la base de données</param>
        /// <param name="currentBet">Pari à mettre à jour, le paramètre "Point" de l'objet contient le nombre de points pour le pari</param>
        /// <exception cref="ArgumentException">Connexion à la base de données est nulle</exception>
        /// <exception cref="ArgumentException">Le pari passé en argument est nul</exception>
        public static void UpdateBetPoint(MyBetDBEntities dbContext, UserBets currentBet)
        {
            if (dbContext == null)
            {
                throw new ArgumentException("BetObject.UpdateBetPoint - dbContext can not be null");
            }
            if (currentBet == null)
            {
                throw new ArgumentException("BetObject.UpdateBetPoint - currentBet can not be null");
            }
            // Récupération du pari existant
            var queryBets = (from bets in dbContext.UserBets
                             where (bets.UserId == currentBet.UserId) && (bets.MatchId == currentBet.MatchId)
                             select bets);
            UserBets existingBets = queryBets.SingleOrDefault();
            if (existingBets == null)
            {
                // Pari inexistant
                throw new Exception.DatabaseException("Inexisting match or unknown user");
            }
            // Mise à jour du score du pari
            existingBets.Point = currentBet.Point;
        }

        /// <summary>
        /// Validation de tous les paris d'un événement
        /// </summary>
        /// <param name="userId">Identifiant du parieur</param>
        /// <param name="eventId">Identifiant de l'événement</param>
        /// <param name="matchId">Liste des identifiants des matchs</param>
        /// <param name="scoreHome">Liste des scores de l'équipe qui reçoit</param>
        /// <param name="scoreAway">Liste des scores de l'équipe qui se déplace</param>
        /// <param name="currentDateUtc">Date UTC courante (paramètre pour les tests unitaires)</param>
        /// <returns>Statut de la validation <seealso cref="PredictionValidationStatus"/></returns>
        public static PredictionValidationStatus SetPrediction(MyBetDBEntities dbContext,
                                          Guid userId,
                                          int eventId,
                                          List<int> matchId,
                                          List<int> scoreHome,
                                          List<int> scoreAway,
                                          DateTime currentDateUtc)
        {
//#if DEBUG
//            //userId = new Guid("{D88B92C6-2A0E-4B27-A3C1-FE6BE24E08B0}");
//#endif

            if (dbContext == null)
            {
                throw new ArgumentException("BetObject.SetPrediction - dbContext can not be null");
            }
            else if (matchId == null)
            {
                throw new ArgumentException("BetObject.SetPrediction - matchId can not be null");
            }
            else if (scoreHome == null)
            {
                throw new ArgumentException("BetObject.SetPrediction - scoreHome can not be null");
            }
            else if (scoreAway == null)
            {
                throw new ArgumentException("BetObject.SetPrediction - scoreAway can not be null");
            }
            else if ((matchId.Count != scoreHome.Count) || (matchId.Count != scoreAway.Count))
            {
                throw new ArgumentException("BetObject.SetPrediction - matchId, scoreHome, scoreAway don't have the same number of items");
            }

            try
            {
                Events currentEvent = EventObject.GetEventFromDatabase(dbContext, eventId);
                if (currentEvent == null)
                {
                    throw new ArgumentException("BetObject.SetPrediction - Unknown event");
                }

                if (currentEvent.CloseDate < currentDateUtc)
                {
                    return PredictionValidationStatus.DateExpired;
                }

                Dictionary<int, UserBets> betList = GetBetsDictionaryInDatabase(dbContext, userId, eventId);
                int i = 0;
                foreach (int id in matchId)
                {
                    UserBets bets = null;
                    if (betList.ContainsKey(id))
                    {
                        bets = betList[id];
                    }

                    if (bets == null)
                    {
                        UserBets bet = new UserBets();
                        bet.UserId = userId;
                        bet.MatchId = id;
                        bet.ScoreHome = scoreHome[i];
                        bet.ScoreAway = scoreAway[i];
                        dbContext.UserBets.AddObject(bet);
                    }
                    else
                    {
                        if ((bets.ScoreHome != scoreHome[i])
                            || (bets.ScoreAway != scoreAway[i]))
                        {
                            bets.ScoreHome = scoreHome[i];
                            bets.ScoreAway = scoreAway[i];
                        }
                    }
                    i++;
                }

                // Mise à jour de la date du pari
                // Récupération de tous les paris de tous les joueurs quelque soit leur compétition
                EventScores queryEventScore = (from eventScore in dbContext.EventScores
                                               where (eventScore.UserId == userId) && (eventScore.EventId == eventId)
                                               select eventScore).FirstOrDefault();

                if (queryEventScore == null)
                {
                    // Création de la ligne dans la table
                    EventScores newEventScore = new EventScores();
                    newEventScore.UserId = userId;
                    newEventScore.EventId = eventId;
                    newEventScore.Score = 0;
                    newEventScore.LastBetUpdate = DateTime.UtcNow;
                    dbContext.EventScores.AddObject(newEventScore);
                }
                else
                {
                    queryEventScore.LastBetUpdate = DateTime.UtcNow;
                }

                dbContext.SaveChanges();

            }
            catch (System.ArgumentException argsException)
            {
                System.Diagnostics.Debug.WriteLine(argsException.Message);
                throw;
            }
            catch (System.Exception ex)
            {
                System.Diagnostics.Debug.WriteLine(ex.Message);
                return PredictionValidationStatus.GeneralFailure;
            }

            return PredictionValidationStatus.Success;
        }

        #endregion

        /// <summary>
        /// Send bet summary by email
        /// </summary>
        /// <param name="user"></param>
        /// <param name="matchs"></param>
        public static void SendBetSummary(String userEmail, string eventName, List<MatchInfo> matchs, List<int> scoreHome, List<int> scoreAway)
        {
            // Envoi par mail
            try
            {
                if ((matchs.Count != scoreHome.Count) || (matchs.Count != scoreAway.Count))
                {
                    System.Diagnostics.Trace.WriteLine("PredictionService.SendBetSummary - Internal error / invalid matchs.count");
                    return;
                }
                MailMessage mail = new MailMessage("administrator@parieurpro.com", userEmail);
                String message = "Bonjour,";
                int currentItem = 0;
                message += Environment.NewLine;
                message += Environment.NewLine;
                message += "Ci-dessous le pari enregistré :";
                message += Environment.NewLine;
                message += Environment.NewLine;
                foreach (MatchInfo item in matchs)
                {
                    message += String.Format("{0} {1} - {2} {3}", item.TeamHomeName, scoreHome[currentItem], scoreAway[currentItem], item.TeamAwayName);
                    message += Environment.NewLine;
                    currentItem++;
                }
                message += Environment.NewLine;
                message += "Equipe ParieurPro.com";
                mail.Subject = String.Format("[ParieurPro] {0} ", eventName);
                mail.Body = message;
                mail.IsBodyHtml = false;
                SmtpClient smtp = new SmtpClient();
                smtp.Send(mail);
            }
            catch (System.Exception ex)
            {
                System.Diagnostics.Trace.WriteLine(String.Format("PredictionService.SendBetSummary", ex));
            }

        }


    }

}